跳到主要内容

Unity 协程相关操作

Invoke 延时执行

Invoke() 是unity内置的一个方法 没有重载,只有两个参数,意思是延时方法,在多少时间后调用一个方法 第一个参数是 字符串格式的方法名称,第二个参数以秒为单位的延时时间

表示3秒后调用fun方法

public class InvokeTest : MonoBehaviour {

void Start(){
Invoke ("fun", 3);
}
private void fun(){
Debug.Log ("invokeTest");
}

}

声音延迟播放

// 以秒为单位
fxSource.PlayDelayed(1.1f);

协程的使用

参考资料 Unity协程的原理与应用

这里是协程的补充知识,协程是 Unity 的异步操作

什么是协程

协程分为两部分,协程与协程调度器:协程仅仅是一个能够中间暂停返回的函数,而协程调度是在 MonoBehaviour 的生命周期中实现的。 准确的说,Unity 只实现了协程调度部分,而协程本身其实就是用了 C# 原生 迭代器方法

C# 中的迭代器方法其实就是一个协程,你可以使用 yield 来暂停,使用 MoveNext() 来继续执行。 当一个方法的返回值写成了 IEnumerator 类型,他就会自动被解析成迭代器方法(后文直接称之为协程),你调用此方法的时候不会真的运行,而是会返回一个迭代器,需要用 MoveNext() 来真正的运行。看例子:

static void Main(string[] args)
{
IEnumerator it = Test();//仅仅返回一个指向Test的迭代器,不会真的执行。
Console.ReadKey();
it.MoveNext();//执行Test直到遇到第一个yield
System.Console.WriteLine(it.Current);//输出1
Console.ReadKey();
it.MoveNext();//执行Test直到遇到第二个yield
System.Console.WriteLine(it.Current);//输出2
Console.ReadKey();
it.MoveNext();//执行Test直到遇到第三个yield
System.Console.WriteLine(it.Current);//输出test3
Console.ReadKey();
}

static IEnumerator Test()
{
System.Console.WriteLine("第一次执行");
yield return 1;
System.Console.WriteLine("第二次执行");
yield return 2;
System.Console.WriteLine("第三次执行");
yield return "test3";
}

1、执行 Test() 不会运行函数体,会直接返回一个 IEnumerator 2、调用 IEnumerator 的 MoveNext() 成员,会执行协程直到遇到第一个 yield return 或者执行完毕。 3、调用 IEnumerator 的 Current 成员,可以获得 yield return 后面接的返回值,该返回值可以是任何类型的对象。

所以协程其实就是一个 IEnumerator(迭代器),IEnumerator 接口有两个方法 CurrentMoveNext(),Unity 在每帧做的工作就是:调用 协程(迭代器)MoveNext() 方法,如果返回 true,就从当前位置继续往下执行。

渐变的例子

让一个游戏物体的颜色渐渐变淡,直至消失。

在函数中写一个循环,让渲染对象的 alpha 从1开始不断递减直至降为0。 但是,函数被调用后将运行到完成状态然后返回,函数内的一切逻辑都会在同一帧内完成。也就是说,Fade 函数中的 alpha 从 1 递减到 0 的过程会在一帧内完成,如果你调用这个 Fade 函数,看到的将会是游戏物体瞬间消失,而不会渐渐淡出。

//错误实现,这会在一帧执行完
void Fade()
{
float alpha = 1.0f;
while(alpha > 0)
{
alpha -= Time.deltaTime;
Color c = renderer.material.color;
c.a = alpha;
renderer.material.color = c;
}
}
// ---------------------------------------------------------------------------------
//正确实现
IEnumerator Fade()
{
float alpha = 1.0f;
while(alpha > 0)
{
alpha -= Time.deltaTime;
Color c = renderer.material.color;
c.a = alpha;
renderer.material.color = c;
yield return null;//协程会在这里被暂停,直到下一帧被唤醒。
}
}

//Fade不能直接调用,需要使用StartCoroutine(方法名(参数列表))的形式进行调用。
StartCoroutine(Fade());

像这种以 IEnumerator 为返回值的函数,在 C# 里面被称之为迭代器函数,在 Unity 里面也可以被称为协程函数。 当协程函数运行到 yield return null 语句时,协程会被暂停,unity 继续执行其它逻辑,并在下一帧唤醒协程。

如何使用

MonoBehaviour.StartCoroutine() 方法可以开启一个协程,这个协程会挂在该 MonoBehaviour 下。在 MonoBehaviour 生命周期的 Update 和 LateUpdate 之间,会检查这个 MonoBehaviour 下挂载的所有协程,并唤醒其中满足唤醒条件的协程。

要想使用协程,只需要以 IEnumerator 为返回值,并且在函数体里面用 yield return 语句来暂停协程并提交一个唤醒条件。然后使用 StartCoroutine 来开启协程。

下面这个实例展示了协程的用法。

IEnumerator CoroutineA(int arg1, string arg2)
{
Debug.Log($"协程A被开启了");
yield return null;
Debug.Log("刚刚协程被暂停了一帧");
yield return new WaitForSeconds(1.0f);
Debug.Log("刚刚协程被暂停了一秒");
yield return StartCoroutine(CoroutineB(arg1, arg2));
Debug.Log("CoroutineB运行结束后协程A才被唤醒");
yield return new WaitForEndOfFrame();
Debug.Log("在这一帧的最后,协程被唤醒");
Debug.Log("协程A运行结束");
}

IEnumerator CoroutineB(int arg1, string arg2)
{
Debug.Log($"协程B被开启了,可以传参数,arg1={arg1}, arg2={arg2}");
yield return new WaitForSeconds(3.0f);
Debug.Log("协程B运行结束");
}

协程的应用场景

1、创建补间动画 补间动画指的是在给定若干个关键帧中插值来实现的动画。 如:给定两个时间点的 Alpha 值,可以插值出一个淡入淡出的动画效果。(创建补间动画更常用的做法是使用 Dotween 插件)

2、打字机效果 很多游戏的人物对话界面中,文字并不是一开始就显示在对话框中的,而是一个一个显示出来的。这种将文本一个一个字的显示出来的效果称之为打字机(Typewriter)。使用协程,可以每显示一个字符后等待若干时间,从而实现打字机效果。 b站上有一个 基于协程的打字机效果的简单实现

3、异步加载资源 资源加载指的是通过 IO 操作,将磁盘或服务器上的数据加载成内存中的对象。资源加载一般是一个比较耗时的操作,如果直接放在主线程中会导致游戏卡顿,通常会放到异步线程中去执行。

例如从服务器上加载一个图片并显示给用户,需要做两件事情:

  1. 通过IO操作从服务器上加载图片数据到内存中。
  2. 当加载完成后,将图片显示在屏幕上。

其中,2操作必须等待1操作执行完毕后才能开始执行。 传统的互联网应用中,一般会使用回调函数来实现类似功能:

//伪代码

//提供给用户的接口
void ShowImageFromUrl(string url)
{
LoadImageAsync(url, Callback); //开启一个异步线程来加载图像,加载完成后会自动调用回调函数
}

//回调函数
void Callback(Image image)
{
Show(image);
}

改写成协程的形式:

//伪代码

IEnumerator ShowImageFromUrl(string url)
{
Image image = null;
yield return LoadImageAsync(url, image); //异步加载图像,加载完成后唤醒协程
Show(image);
}

4、定时器操作

void Start () {
StartCoroutine(Destroy());
}

IEnumerator Destroy() {
yield return WaitForSeconds(3.0f);
Destroy(gameObject);
}